package com.jflm.service.impl;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import com.jflm.service.ISeqService;
import com.jflm.util.DateUtil;
import com.jflm.util.RedisPool;

/**
 * ******************  类说明  *********************
 * class       :  SeqServiceImpl
 * @author     :  hejinyun@umfintech.com
 * @version    :  1.0  
 * description :  序列生成接口
 * @see        :                        
 * ***********************************************
 */
@Service("seqService")
public class SeqServiceImpl implements ISeqService {
	
	private static final Logger LOG = LoggerFactory.getLogger(SeqServiceImpl.class);
	
	private final int MAX_INCREMENT = 999999;
	/**
	 * seed获取锁
	 */
	private final Lock lock = new ReentrantLock();
	/**
	 * Sequence持久化因子
	 */
	private volatile Integer seed = -1;
	/**
	 * 增长因子
	 */
	private AtomicInteger increment = new AtomicInteger(0);
	
	@Override
	public long getNextValue(String sequenceName) throws Exception {
		
		int incr = increment.getAndIncrement();
		int seed = this.seed;

		// 如果seed值未初始化，则先初始化seed
		if (seed == -1) {
			lock.lock();
			try {
				if ((seed = this.seed) == -1)
					seed = getSeedAndCache(sequenceName);
			} finally {
				lock.unlock();
			}
		}
		
		// 如果自增变量超过最大允许值，则重新获取seed，并循环increment.
		if (incr > MAX_INCREMENT) {
			lock.lock();
			try {
				incr = increment.getAndIncrement();
				// 超过了9999（MAX_INCREMENT）后，要么自己把seed修改掉，要么用别的线程修改过的seed
				if (incr > MAX_INCREMENT) {// double check.
					// generate seed first to avoid old seed being used with
					// new cycle increment to generate conflict sequence.
					seed = getSeedAndCache(sequenceName);
					increment.set(1);// cycle the increment.
					incr = 0;
				} else {
					seed = this.seed;
				}
			} finally {
				lock.unlock();
			}
		}

		final String sequence = new StringBuilder(DateUtil.getReqDate())
				.append(lpad(seed, 4)).append(lpad(incr, 6)).toString();

		LOG.debug("generate sequence for {}:{}", sequenceName, sequence);

		return Long.parseLong(sequence);
	}
	
	private String lpad(int num, int radix) {
		if (num < 0)
			throw new IllegalArgumentException("illegal positive number:" + num);

		if (num >= Math.pow(10, radix))
			throw new IllegalArgumentException("number overflow: " + num + ", radix: " + radix);

		if (num == 0)
			return lpad0(num, radix - 1);

		for (int i = 0; i < radix; i++) {
			if (num < Math.pow(10, i)) {
				return lpad0(num, radix - i);
			}
		}

		return String.valueOf(num);
	}
	
	private String lpad0(int num, int pad) {
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < pad; i++) {
			sb.append("0");
		}
		return sb.append(num).toString();
	}
	
	/**
	 * ********************************************
	 * method name   : getSeedAndCache 
	 * description   : 获取seed因子
	 * @return       : int
	 * @param        : @param sequenceName
	 * @param        : @return
	 * @param        : @throws Exception
	 * modified      : hejinyun@umfintech.com ,  2017-6-16  上午11:26:50
	 * @see          : 
	 * *******************************************
	 */
	private int getSeedAndCache(String sequenceName) throws Exception {
		RedisPool.incrBy(sequenceName, 1);
		
		int seed = Long.valueOf(RedisPool.get(sequenceName)).intValue();
		seed -= ((seed / 10000) * 10000);// 仅取后4位数字
		LOG.info("get seed from redis for {}:{}", sequenceName, seed);
		this.seed = seed;
		
		return seed;
	}
}
